import java.util.ArrayList;

public class InsRem 
{
	private ArrayList<Tuple> inOut;
	private boolean valuesAreSame;
	private boolean indexesAreSame;
	private ArrayList<Integer> comparisonValues;
	private ArrayList<Integer> comparisonElemIndexes;
	
	public InsRem(ArrayList<Tuple> inOut)
	{
		this.inOut = inOut;
		valuesAreSame = false;
		indexesAreSame = false;
		
		comparisonValues = new ArrayList<Integer>();
		comparisonElemIndexes = new ArrayList<Integer>();
	}

	
	public void checkInsertOrRemove()
	{
		if(this.sameOutputLengthDiff())
		{
			if(this.getExtraLength() == 0) // if we get here then this isn't a case of insert/append or removal (most likely)
				System.out.println("Input and output lists are of same length - probably not insert/add or remove operations.");
			else if(this.getExtraLength() > 0) //output is bigger, new elements added
			{
				this.findPatt(this.getNewElems(), false);
				this.printConclusions(false); //false = not removal - it's insert/add
			}
			else								//input is bigger, elements has been removed
			{
				this.findPatt(this.getRemovedElems(), true);
				this.printConclusions(true);
			}
		}
		else
		{
			System.out.println("The output lists differ in length among themselves - probably not insert/add or remove operations (or very complicated ones).");
			//TODO:check the pair(s) that are different and see what makes them unique. 
			//First thing that comes to mind is checking the values of the two neighbor elements.
		}
	}
	
	public boolean sameOutputLengthDiff()
	{
		int j = 0;
		
		if(inOut.size() >= 1)
			j = inOut.get(0).get(2).size();
		
		for(int i=0;i<inOut.size();i++)
		{
			if(j != inOut.get(i).get(2).size())
				return false;
		}
		
		return true;
	}
	
	public int getExtraLength()
	{
		if(inOut.size() >= 1)
			return inOut.get(0).get(2).size() -  inOut.get(0).get(1).size();
		else
			return 0;
	}
	
	public int getLengthOffset() //same as above but returns a positive value that you gotta check urself if it's removal or adding
	{
		if(inOut.size() >= 1)
		{
			if(inOut.get(0).get(2).size() -  inOut.get(0).get(1).size() < 0)
				return (inOut.get(0).get(2).size() -  inOut.get(0).get(1).size()) * -1;
			else
				return inOut.get(0).get(2).size() -  inOut.get(0).get(1).size();
		}
		else
			return 0;
	}
	
	public ArrayList<PattElem> getNewElems()
	{
		ArrayList<PattElem> p = new ArrayList<PattElem>();
		
		ArrayList<Tuple> tempInOut = new ArrayList<Tuple>();
		this.listCopy(tempInOut, inOut);
		
		int k = 0;
		boolean storeRemaining = false;
		
		for(int i=0;i<tempInOut.size();i++)
		{
			for(int j=0;j<tempInOut.get(i).get(2).size();j++)
			{
				//if an elem from output list doesn't exist in input list, that's a newly added elem and we store it and its info.
				// [1, 2, 3] -> [1, 1, 2, 3, 3]
				// [1, 2, 3] -> [1, 4, 7, 2, 3]
				
				// if we get here it means the new elements are appended to end of output list so we store all remaining ones
				
				if(k == tempInOut.get(i).get(1).size())
					storeRemaining = true;
				
				int tempInputValue = 0;
				if(!storeRemaining)
					tempInputValue = tempInOut.get(i).get(1).get(k);
				
				int tempOutputValue = tempInOut.get(i).get(2).get(j);
				
				if((tempInputValue != tempOutputValue) || storeRemaining)
				{
					p.add(new PattElem( tempOutputValue, j, i));
					continue;
				}		
				k++;
			}
			storeRemaining = false;
			k = 0;
		}
		return p;
	}
	
	public ArrayList<PattElem> getRemovedElems()
	{
		ArrayList<PattElem> p = new ArrayList<PattElem>();
		
		ArrayList<Tuple> tempInOut = new ArrayList<Tuple>();
		this.listCopy(tempInOut, inOut);
		
		int k = 0;
		boolean storeRemaining = false;
		
		for(int i=0;i<tempInOut.size();i++)
		{
			for(int j=0;j<tempInOut.get(i).get(1).size();j++)
			{
				//if an elem from input list doesn't exist in output list, that's probably a removed elem and we store it and its info.
				// [1, 2, 3, 4, 5] -> [1, 2, 3]
				// [1, 2, 6, 7, 2] -> [1, 2, 2]
				
				if(k == tempInOut.get(i).get(2).size())
					storeRemaining = true;
				
				int tempOutputValue = 0;
				if(!storeRemaining)
					tempOutputValue = tempInOut.get(i).get(2).get(k);
				
				int tempInputValue = tempInOut.get(i).get(1).get(j);
				
				if((tempInputValue != tempOutputValue) || storeRemaining)
				{
					p.add(new PattElem( tempInputValue, j, i));
					continue;
				}		
				k++;
			}
			storeRemaining = false;
			k = 0;
		}
		return p;
	}
	
	public void findPatt(ArrayList<PattElem> p, boolean isRemoval)
	{
		
		//We store up all values/indexes taken from 1 pair (chosen at random basically) and compare them to the rest of the 'new' element values/indexes of other pairs 
		//to get an idea on what basis they are removed OR added/inserted.
		//If values are different on insert/add (but indexes are same), then their values are most likely based on what the other values in that input list are (complicated stuff)
		//If indexes are different on insert/add (but values are same), then the indexes are probably chosen because of specific neighbor (the 1-2 nearby) values in the input list
		
		if((this.getLengthOffset() * inOut.size() ) < p.size())
		{
			System.out.println("The number of changed elements is greater than the number of added/removed elements  - this is probably not a remove/add pattern");
			return;
		}
		
		int examplePairIndex = 0;
		
		if(!p.isEmpty())
		{
			examplePairIndex = p.get(0).getPairIndex();

			comparisonValues.add(p.get(0).getValue());
			comparisonElemIndexes.add(p.get(0).getElemIndex());
			
			for(int i=0;i<p.size();i++)
				System.out.println("v: " + p.get(i).getValue() + " eI: " + p.get(i).getElemIndex() + " pI " + p.get(i).getPairIndex());
			
			for(int i=1;i<p.size();i++) // i=1 or otherwise we had from pair 0 twice. We do this loop because a pair might contain more than 1 new/removed element.
			{
				if(examplePairIndex == p.get(i).getPairIndex())
				{
					comparisonValues.add(p.get(i).getValue());
					comparisonElemIndexes.add(p.get(i).getElemIndex());
				}
			}
		}
		else
		{
			System.out.println("p is empty");
			return;
		}
		
		valuesAreSame = false;
		
		for(int i=0;i<p.size();i++)
		{
			if(p.get(i).getPairIndex() != examplePairIndex )
			{
				valuesAreSame = false;
				for(int j=0;j<comparisonValues.size();j++)
				{
					if(p.get(i).getValue() == comparisonValues.get(j))
						valuesAreSame = true;
				}
				if(!valuesAreSame)
					break;
			}
		}
		
		indexesAreSame = false;
		
		for(int i=0;i<p.size();i++)
		{
			if(p.get(i).getPairIndex() != examplePairIndex )
			{
				indexesAreSame = false;
				for(int j=0;j<comparisonElemIndexes.size();j++)
				{
					if(p.get(i).getElemIndex() == comparisonElemIndexes.get(j))
						indexesAreSame = true;
	
				}
				if(!indexesAreSame)
					break;
			}
		}
	}
	
	public void printConclusions(boolean isRemoval)
	{
		
		//insert of fixed values in fixed positions. 
		//TODO: check if it's append or insert, and if insert, by what principle/method
		
		if(valuesAreSame && indexesAreSame)	
		{
			if(isRemoval)
			{
				System.out.print("Lack of input examples makes it impossible to determine if removal is done by element value or element index - ");
				System.out.println("the output list is created by either: ");
				System.out.print("(1) removing " + this.getLengthOffset() + " elements with the values: ");
				this.printValues(comparisonValues);
	
				System.out.print("(2) removing " + this.getLengthOffset() + " elements at the indexes: ");
				this.printElemIndexes(comparisonElemIndexes);
				System.out.println("(2) is most likely though.");
			}
			else
			{
				System.out.println("The output list is created by adding " + this.getLengthOffset() + " elements with the values: ");
				this.printValues(comparisonValues);
				
				System.out.println("at the indexes: ");
				this.printElemIndexes(comparisonElemIndexes);
			}
		}
		else if(valuesAreSame && !indexesAreSame)
		{
			if(isRemoval)
			{
				System.out.println("The output list is created by removing the " + this.getLengthOffset() + " elements with the values: ");
				this.printValues(comparisonValues);
			}
			else
			{
				System.out.println("The output list is created by adding " + this.getLengthOffset() + " elements with the values: ");
				this.printValues(comparisonValues);
				System.out.println("The position of the values depends somehow on input list - unknown method/basis/pattern.");
			}
		}
		else if(!valuesAreSame && indexesAreSame)
		{
			if(isRemoval)
			{
				System.out.println("The output list is created by removing the " + this.getLengthOffset() + " elements at the indexes: ");
				this.printElemIndexes(comparisonElemIndexes);
			}
			else
			{
				System.out.println("The output list is created by adding " + this.getLengthOffset() + " elements at the indexes: ");
				this.printElemIndexes(comparisonElemIndexes);
				System.out.println("The values of the elements depends somehow on input list - unknown method/basis/pattern.");
			}
		}
		else if(!valuesAreSame && !indexesAreSame)
		{
			if(isRemoval)
				System.out.println("Both indexes and values differ between pairs - no idea on method/pattern");
			else
				System.out.println("Both indexes and values differ between pairs - no idea on method/pattern");
		}
	}
	
	public void printElemIndexes(ArrayList<Integer> comparisonElemIndexes)
	{
		for(int i=0;i<comparisonElemIndexes.size();i++)
		{
			System.out.print(comparisonElemIndexes.get(i));
			if(i + 1 != comparisonElemIndexes.size())
				System.out.print(", ");
		}
		System.out.println();
	}
	
	public void printValues(ArrayList<Integer> comparisonValues)
	{
		for(int i=0;i<comparisonValues.size();i++)
		{
			System.out.print(comparisonValues.get(i));
			if(i + 1 != comparisonValues.size())
				System.out.print(", ");
		}
		System.out.println();
	}
	
	public void listCopy(ArrayList<Tuple> dst, ArrayList<Tuple> src)
	{
		for(int i=0;i<src.size();i++)
		{
			Tuple t = new Tuple();
			t.set(src.get(i).get(1), 1);
			t.set(src.get(i).get(2), 2);
			dst.add(t);
		}
	}
}
